/*
 * Copyright 2011 The Rusted Roof, LLC.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

package org.proteusframework.platformservice.persistence.vfs;

import java.io.File;
import java.io.Serializable;
import java.util.*;

/**
 * /**
 * Abstract representation of a file path. A file path is represented as a list of path elements. These elements
 * typically represent directories on disk. The abstract path is a relative path. It can be converted to an absolute
 * system path by interpreting the abstract path relative to a root directory. For example:
 * <p/>
 * <pre>
 * FilePath path = new FilePath(IVirtualFileSystem.DEVICE_NAMESPACE, "com.acme", "AcmeDevice", "1");
 * path.toString();   // Returns "Devices/com.acme/AcmeDevice/1"
 * path.toSystemPath(new File("C:\\Users\\AcmeUser")); // Returns "C:\Users\AcmeUser\Devices\com.acme\AcmeDevice\1"
 * </pre>
 *
 * @author Tacoma Four
 */
public class FilePath implements Cloneable, Serializable
{

    public static final String SEPARATORS = "\\/";

    private final ArrayList<String> path = new ArrayList<String>(); //NOPMD - Use ArrayList so it is serializable

    /**
     * Create an abstract path from a path string. Any character in the {@link #SEPARATORS} string will be interpreted
     * as a path separator. The path may optionally begin with a separator. For example,
     * {@code new FilePath("/Devices/com.acme/AcmeDevice/1")} results in the abstract path components "Devices",
     * "com.acme", "AcmeDevice", "1".
     *
     * @param path Path string.
     */
    public FilePath(String path)
    {
        add(path);
    }

    /**
     * Create an abstract path from a list of path components. Any character in the {@link #SEPARATORS} string will
     * be interpreted as a path seperator.
     *
     * @param path Path elements.
     */
    public FilePath(List<String> path)
    {
        addAll(path);
    }

    /**
     * Create an abstract path from a list of path components. Any character in the {@link #SEPARATORS} string will
     * be interpreted as a path seperator. For example {@code new FilePath("Devices", "com.acme", "AcmeDevice/1")}
     * results in the abstract path elemnts "Devices", "com.acme", "AcmeDevice", "1".
     *
     * @param path Path elements.
     */
    public FilePath(String... path)
    {
        if (null != path)
        {
            addAll(Arrays.asList(path));
        }
    }

    /**
     * Append an element to the path.
     *
     * @param name New element.
     */
    public void append(String name)
    {
        if (name == null)
        {
            throw new IllegalArgumentException("Parameter 'name' may not be null");
        }
        add(name);
    }

    /**
     * Get the parent of the file or directory specified by this path. The parent is the second to last path
     * element in the list. For example, in the path "Devices/com.acme/AcmeDevice/1/acmeFile.txt",
     * "Devices/com.acme/AcmeDevice/1" is the parent.
     *
     * @return File or directory parent's path.
     */
    public FilePath getParent()
    {
        if (path.size() > 1)
        {
            return new FilePath(path.subList(0, path.size() - 1));
        } else
        {
            return new FilePath(SEPARATORS);
        }
    }

    /**
     * Get the path of the file or directory specified by this path.
     *
     * @return File or directory path.
     */
    public FilePath getPath()
    {
        if (path.size() >= 1)
        {
            return new FilePath(path.subList(0, path.size()));
        } else
        {
            return new FilePath(SEPARATORS);
        }
    }

    /**
     * Get the file (or directory) name specified by this path. The name is the last path element in the list.
     * For example, in the path "Devices/com.acme/AcmeDevice/1/acmeFile.txt", "acmeFile.txt" is the name.
     *
     * @return File or directory name.
     */
    public String getName()
    {
        if (!path.isEmpty())
        {
            return path.get(path.size() - 1);
        } else
        {
            return "";
        }
    }

    /**
     * Clone the abstract path.
     *
     * @return A deep copy of the path.
     */
    @Override
    @SuppressWarnings({"CloneDoesntCallSuperClone", "PMD.ProperCloneImplementation"})
    public FilePath clone() throws CloneNotSupportedException
    {
        return new FilePath(path);
    }

    /**
     * Convert the abstract path to an array of path elements. For example, the abstract path
     * "/Devices/com.acme/AcmeDevice" will convert to {@code ["Devices", "com.acme", "AcmeDevice"]}.
     *
     * @return Array representation of the file path.
     */
    public String[] toArray()
    {
        return path.toArray(new String[path.size()]);
    }

    /**
     * Convert the abstract path to a system path.
     *
     * @param root The root directory of the Virtual File System.
     * @return System path for the abstract VFS path.
     */
    public String toSystemPath(File root)
    {
        return (new File(root, this.toString())).getAbsolutePath();
    }

    /**
     * Convert the path to a String. The path elements will be joined with "/" characters. For example, the path
     * elements "Devices", "com.acme", "AcmeDevice", "1" yeild the string representation "Devices/com.acme/AcmeDevice/1".
     *
     * @return A string representation of the file path.
     */
    @Override
    public String toString()
    {
        if (!path.isEmpty())
        {
            StringBuilder sb = new StringBuilder();
            boolean first = true;

            for (String el : path)
            {
                if (first)
                {
                    first = false;
                } else
                {
                    sb.append("/");
                }
                sb.append(el);
            }
            return sb.toString();
        } else
        {
            return "/";
        }
    }

    @Override
    public boolean equals(Object o)
    {
        if (!(o instanceof FilePath))
        {
            return false;
        }

        FilePath otherPath = (FilePath) o;

        return toString().equals(otherPath.toString());
    }

    @Override
    public int hashCode()
    {
        return toString().hashCode();
    }

    private void addAll(Collection<String> values)
    {
        for (String value : values)
        {
            add(value);
        }
    }

    /**
     * Append a string to the path. If the string contains seperator characters it will be broken into multiple path elements.
     *
     * @param value String to append.
     */
    private void add(String value)
    {
        if (null != value)
        {
            StringTokenizer st = new StringTokenizer(value, SEPARATORS);
            while (st.hasMoreTokens())
            {
                addSingleElement(st.nextToken());
            }
        }
    }

    /**
     * Alter path elements that Windows will reject by
     * prepending an underscore to them.
     *
     * @param value The value to add to the path.
     */
    private void addSingleElement(String value)
    {
        String normalizedValue = value.toUpperCase();

        // Modify folder values that are
        // illegal in Windows
        if (normalizedValue.matches("COM[0-9]")
                || normalizedValue.matches("LPT[0-9]")
                || "CON".equals(normalizedValue)
                || "PRN".equals(normalizedValue)
                || "AUX".equals(normalizedValue)
                || "NUL".equals(normalizedValue))
        {
            path.add("_" + value);
        } else
        {
            path.add(value);
        }

    }
}

